Step 31: Schema Validation

Let me remind you of the Bookmark schema:

import mongoose from "mongoose";

const BookmarkSchema = new mongoose.Schema({
  title: { type: String, required: true },
  url: { type: String, required: true },
});

const Bookmark = mongoose.model("Bookmark", BookmarkSchema);

export default Bookmark;

So what will happen if we make a bookmark and fail to provide a title? Let’s try it! Update the test in tests/mode/Bookmark.test.js and set title to null

test("test constructor", async () => {
  db.connect(process.env.DB_TEST_URI);
  const title = null;
  const url = faker.internet.url();
  const bookmark = await Bookmark.create({ title, url });
  expect(bookmark.title).toBe(title);
  expect(bookmark.url).toBe(url);
  expect(bookmark.id).toBeDefined();
  await Bookmark.deleteMany({});
});

Run this test. You should see a validation error generated by mongoose! By the way, you can provide more detailed error messages. Update src/model/Bookmark.js as follows:

import mongoose from "mongoose";

const BookmarkSchema = new mongoose.Schema({
  title: { type: String, required: [true, "Each bookmark must have a title"] },
  url: { type: String, required: [true, "Each bookmark must have a URL"] },
});

const Bookmark = mongoose.model("Bookmark", BookmarkSchema);

export default Bookmark;

We should test for erroneous inputs. Here is an example where we can do that.

test("test title is null", async () => {
  try {
    const title = null;
    const url = faker.internet.url();
    await Bookmark.create({ title, url });
  } catch (err) {
    expect(err).toBeDefined();
  }
});

Update tests/mode/Bookmark.test.js as follows to add various error handling tests:

import { describe, beforeAll, afterAll, expect, it } from "vitest";
import Bookmark from "../../src/model/Bookmark.js";
import { faker } from "@faker-js/faker";
import * as db from "../../src/data/db.js";
import * as dotenv from "dotenv";

dotenv.config();

describe("Test Bookmark Schema & Model", () => {
  beforeAll(async () => {
    db.connect(process.env.DB_TEST_URI);
    await Bookmark.deleteMany({});
  });

  it("test create bookmark", async () => {
    const title = faker.lorem.sentence();
    const url = faker.internet.url();
    const bookmark = await Bookmark.create({ title, url });
    expect(bookmark.title).toBe(title);
    expect(bookmark.url).toBe(url);
    expect(bookmark.id).toBeDefined();
  });

  describe("test title is required", () => {
    it("test title is null", async () => {
      try {
        const title = null;
        const url = faker.internet.url();
        await Bookmark.create({ title, url });
      } catch (err) {
        expect(err).toBeDefined();
      }
    });

    it("test title is undefined", async () => {
      try {
        const title = undefined;
        const url = faker.internet.url();
        await Bookmark.create({ title, url });
      } catch (err) {
        expect(err).toBeDefined();
      }
    });

    it("test title is empty", async () => {
      try {
        const title = "";
        const url = faker.internet.url();
        await Bookmark.create({ title, url });
      } catch (err) {
        expect(err).toBeDefined();
      }
    });
  });

  describe("test url is required", () => {
    it("test url is null", async () => {
      try {
        const title = faker.lorem.sentence();
        const url = "";
        await Bookmark.create({ title, url });
      } catch (err) {
        expect(err).toBeDefined();
      }
    });

    it("test url is undefined", async () => {
      try {
        const title = faker.lorem.sentence();
        const url = "";
        await Bookmark.create({ title, url });
      } catch (err) {
        expect(err).toBeDefined();
      }
    });

    it("test url is empty", async () => {
      try {
        const title = faker.lorem.sentence();
        const url = "";
        await Bookmark.create({ title, url });
      } catch (err) {
        expect(err).toBeDefined();
      }
    });
  });

  afterAll(async () => {
    await Bookmark.deleteMany({});
  });
});

Run the tests in this file and make sure they all pass. Then, save and commit changes.